// 此文件 用于各种类型检查和转换的实用函数的集合
import { percent, Percent, isPercent } from "./03-Percent";
// 可选类型
export type Optional<A> = A | undefined;
/**
 * @todo Description
 * @ignore Exclude from docs
 */
export type Public<T> = { [P in keyof T]: T[P] };

/**
 * `Keyof<T>` is the same as `keyof T` except it only accepts string keys, not numbers or symbols.
 * `Keyof<T>`与“Keyof T”相同，只是它只接受字符串键，不接受数字或符号。
 */
export type Keyof<T> = Extract<keyof T, string>;

// 如果是NaN 返回true
export function isNaN(value: number): boolean {
  return Number(value) !== value;
}

// 表示所有可用JavaScript变量类型的类型。
export type Type =
  | "[object Object]"
  | "[object Array]"
  | "[object String]"
  | "[object Number]"
  | "[object Boolean]"
  | "[object Date]";

//   调用类型判断 改变上下文
export function getType<A>(value: A): Type {
  return {}.toString.call(value) as Type;
}

/**
 * Returns a default value if the passed in value is empty.
 * 传入的值为空 则返回默认值
 *
 * @param value     Input value
 * @param optional  Default value
 * @return Value or default value whichever is available
 * @deprecated Not used anywhere
 */
export function getDefault<A>(value: null | undefined | A, optional: A): A {
  return value || optional;
}
// 检查传入的值 是否是字符串
export function checkString(value: any): value is string {
  if (typeof value === "string") {
    return true;
  } else {
    throw new Error("Expected a string but got " + getType(value));
  }
}
// 是否布尔
export function checkBoolean(value: any): value is boolean {
  if (typeof value === "boolean") {
    return true;
  } else {
    throw new Error("Expected a boolean but got " + getType(value));
  }
}
// 是否数字 首先校验是否是number类型 然后校验是否是NaN
export function checkNumber(value: any): value is number {
  if (typeof value === "number") {
    if (isNaN(value)) {
      throw new Error("Expected a number but got NaN");
    }
  } else {
    throw new Error("Expected a number but got " + getType(value));
  }
  return true;
}
// 是否为对象
export function checkObject(value: {}): value is {} {
  const t = getType(value);

  if (t === "[object Object]") {
    return true;
  } else {
    throw new Error("Expected an object but got " + t);
  }
}

// 是否是数组
export function checkArray<A>(value: Array<A>): value is Array<A> {
  if (Array.isArray(value)) {
    return true;
  } else {
    throw new Error("Expected an array but got " + getType(value));
  }
}

// 日期对象
export function checkDate(value: Date): value is Date {
  const t = getType(value);

  if (t === "[object Date]") {
    return true;
  } else {
    throw new Error("Expected a date but got " + t);
  }
}

// 字符串或者数字转换为字符串
export function castString(value: string | number): string {
  if (typeof value === "string") {
    return value;
  } else if (typeof value === "number") {
    return "" + value;
  } else {
    throw new Error("Expected a string or number but got " + getType(value));
  }
}

// 字符串 日期或者数字 转换为数字
export function castNumber(value: string | number | Date): number {
  if (typeof value === "string") {
    const number = +value;
    if (isNaN(number)) {
      throw new Error(
        "Cannot cast string " + JSON.stringify(value) + " to a number"
      );
    } else {
      return number;
    }
  } else if (typeof value === "number") {
    if (isNaN(value)) {
      throw new Error("Expected a number but got NaN");
    } else {
      return value;
    }
  } else {
    const t = getType(value);
    if (t === "[object Date]") {
      return value.getTime();
    } else {
      throw new Error("Expected a string, number, or date but got " + t);
    }
  }
}

/**
 * Converts any value into `boolean`.
 *
 * @param value  Source value
 * @return `true` or `false`
 */
export function toBoolean(value: any): boolean {
  return value ? true : false;
}

/**
 * Converts any value into a `number`.
 *
 * @param value  Source value
 * @return Number representation of value
 */
export function toNumber(value: any): number {
  if (hasValue(value) && !isNumber(value)) {
    let converted = Number(value);
    if (isNaN(converted) && isString(value) && value != "") {
      return toNumber(value.replace(/[^0-9.\-]+/g, ""));
    }
    return converted;
  }
  return value;
}

/**
 * Converts any value into a string (text).
 *
 * @param value  Source value
 * @return String representation of the input
 */
export function toText(value: any): string {
  if (hasValue(value) && !isString(value)) {
    if (isNumber(value)) {
      return castString(value);
    } else if (isObject(value)) {
      return value.toString();
    }
  }
  return value;
}

/**
 * Converts any value to a number or [[Percent]].
 *
 * If the parameter is a string and contains "%", it will
 * convert it into a [[Percent]].
 *
 * Otherwise, it will convert into a number.
 *
 * @param value  Number or percent
 * @return Percent object
 */
export function toNumberOrPercent(value: any): number | Percent {
  if (!hasValue(value) || isNumber(value) || isPercent(value)) {
    return value;
  }
  if (isString(value) && value.indexOf("%") != -1) {
    return percent(toNumber(value));
  }
  return toNumber(value);
}

/**
 * ============================================================================
 * VALUE PRESENCE CHECK
 * ============================================================================
 * @hidden
 */

/**
 * Checks if a variable has a value.
 *
 * @param a  Input value
 * @returns                        Has value?
 */
export function hasValue<A>(a: Optional<A> | null): a is A {
  return a != null;
}

/**
 * Returns a value or throws an {Error} exception if the variable has not
 * value.
 *
 * @param a  Input value
 * @returns                        Value
 */
export function getValue<A>(a: Optional<A> | null): A {
  if (hasValue(a)) {
    return a;
  } else {
    throw new Error("Value doesn't exist");
  }
}

/**
 * Returns a value, or returns the default value if it doesn't have a value.
 *
 * @param a  Input value
 * @returns                        Value
 */
export function getValueDefault<A>(a: Optional<A> | null, defaultValue: A): A {
  if (hasValue(a)) {
    return a;
  } else {
    return defaultValue;
  }
}

/**
 * ============================================================================
 * TYPE CHECK
 * ============================================================================
 * @hidden
 */

/**
 * Checks if parameter is `Date`.
 *
 * @param value  Input value
 * @return Is Date?
 */
export function isDate(value: any): value is Date {
  return getType(value) === "[object Date]";
}

/**
 * Checks if parameter is `string`.
 *
 * @param value  Input value
 * @return Is string?
 */
export function isString(value: any): value is string {
  return typeof value === "string";
}

/**
 * Checks if parameter is `number`.
 *
 * @param value  Input value
 * @return Is number?
 */
export function isNumber(value: any): value is number {
  return typeof value === "number" && Number(value) == value;
}

/**
 * Checks if parameter is `object`.
 *
 * @param value  Input value
 * @return Is object?
 */
export function isObject(value: any): value is object {
  return typeof value === "object" && value != null;
}

/**
 * Checks if parameter is `Array`.
 *
 * @param value  Input value
 * @return Is Array?
 */
export function isArray(value: any): value is Array<any> {
  return Array.isArray(value);
}
